#include <os/list.h>
#include <os/mm.h>
#include <os/sched.h>
#include <pgtable.h>
#include <os/string.h>
#include <assert.h>

ptr_t memCurr = FREEMEM;

static LIST_HEAD(freePageList);

ptr_t allocPage()
{
    // TODO:
    ptr_t ret;
    if(list_empty(&freePageList)){
        ret = ROUND(memCurr, PAGE_SIZE);
        memCurr = ret + PAGE_SIZE;
    }else{
        list_node_t *freenode = freePageList.next;
        list_del(freenode);
        ret = freenode;
    }
    clear_pgdir(ret);
    return ret;
}

void free_process_page(uintptr_t pgdir)
{
    uint32_t i, j, k;
    PTE first_pte, second_pte, third_pte;
    uint64_t second_pgdir, third_pgdir;
    for(i = 0; i < 0x100; i++)
    {
        first_pte = *(PTE*)(pgdir + i * 8);
        if((first_pte % 2) != 0)
        {
            second_pgdir = pa2kva(get_pa(first_pte));
            for(j = 0; j < 0x200; j++)
            {
                second_pte = *(PTE*)(second_pgdir + j * 8);
                if((second_pte % 2) != 0)
                {
                    third_pgdir = pa2kva(get_pa(second_pte));
                    for(k = 0; k < 0x200; k++)
                    {
                        third_pte = *(PTE*)(third_pgdir + k * 8);
                        if((third_pte % 2) !=0)
                            freePage(pa2kva(get_pa(third_pte)));
                    }
                    freePage(third_pgdir);
                }
            }
            freePage(second_pgdir);
        }
    }
    freePage(pgdir);
}

void freePage(ptr_t kva)
{
    // TODO:
    list_node_t *freenode;
    uint64_t addr;
    addr = ((uint64_t)kva) & ~(PAGE_SIZE - 1);
    freenode = addr;
    list_add(freenode, &freePageList);
}

void *kmalloc(size_t size)
{
    // TODO(if you need):
}

uintptr_t shm_page_get(int key)
{
    // TODO(c-core):
}

void shm_page_dt(uintptr_t addr)
{
    // TODO(c-core):
}

/* this is used for mapping kernel virtual address into user page table */
void share_pgtable(uintptr_t dest_pgdir, uintptr_t src_pgdir)
{
    // TODO:
    uint32_t len = 0x800;
    kmemcpy((uint8_t *)(dest_pgdir + 0x800), (uint8_t *)(src_pgdir + 0x800), len);
}

/* allocate physical page for `va`, mapping it into `pgdir`,
   return the kernel virtual address for the page.
   */
uintptr_t alloc_page_helper(uintptr_t va, uintptr_t pgdir)
{
    // TODO:
    uint64_t vpn2, vpn1, vpn0;
    uint64_t second_pgdir, third_pgdir;
    uint64_t pgdir_entry;
    vpn2 = va >> 30;
    vpn1 = (va << 34) >> 55;
    vpn0 = (va << 43) >> 55;

    pgdir_entry = *(PTE*)(pgdir + vpn2 * 8);
    if((pgdir_entry % 2) == 0){        //invalid
        second_pgdir = allocPage();
        uint64_t first_ppn = ((second_pgdir - 0xffffffc000000000) / 4096) << 10;
        *(PTE*)(pgdir + vpn2 * 8) = first_ppn | _PAGE_PRESENT | _PAGE_USER | _PAGE_ACCESSED | _PAGE_DIRTY;
    }else{
        second_pgdir = pa2kva(get_pa(pgdir_entry));
    }

    pgdir_entry = *(PTE *)(second_pgdir + vpn1 * 8);
    if((pgdir_entry % 2)==0){
        third_pgdir = allocPage();
        uint64_t second_ppn = ((third_pgdir - 0xffffffc000000000) / 4096) << 10;
        *(PTE*)(second_pgdir + vpn1 * 8) = second_ppn | _PAGE_PRESENT | _PAGE_USER | _PAGE_ACCESSED | _PAGE_DIRTY;
    }else{
        third_pgdir = pa2kva(get_pa(pgdir_entry));
    }


    uint64_t kva = allocPage();       
    uint64_t third_ppn = ((kva - 0xffffffc000000000) / 4096) << 10;
    *(PTE*)(third_pgdir + vpn0 * 8) = third_ppn | _PAGE_PRESENT | _PAGE_USER | _PAGE_READ | _PAGE_WRITE | _PAGE_EXEC | _PAGE_ACCESSED | _PAGE_DIRTY;    
    return kva;
}

void init_freePage(){
    init_list_head(&(freePageList));
}